/*  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "luge-color.hh"
#include <lauxlib.h>
#include <string>
#include <cstring>
#include <sstream>

namespace {

/**
 * color objects are defined by a metatable with this name.
 */
static const char mtname_color[] = "-luge:color";

/*
 * metafunctions for the color type.
 */
static int color_gc (lua_State *ls);
static int color_tostring (lua_State *ls);
static int color_index (lua_State *ls);
static int color_newindex (lua_State *ls);

/**
 * push the metatable for colors onto the stack.
 */
static void push_color_metatable (lua_State *ls)
{
  if (luaL_newmetatable (ls, mtname_color)) {
    const int color_table = lua_absindex (ls, -1);
    lua_pushcclosure (ls, color_gc, 0);
    lua_setfield (ls, color_table, "__gc");
    lua_pushcclosure (ls, color_tostring, 0);
    lua_setfield (ls, color_table, "__tostring");
    lua_pushcclosure (ls, color_index, 0);
    lua_setfield (ls, color_table, "__index");
    lua_pushcclosure (ls, color_newindex, 0);
    lua_setfield (ls, color_table, "__newindex");
  }
}

static int color_gc (lua_State *ls)
{
  GeglColor *color = *static_cast <GeglColor **> (lua_touserdata (ls, 1));
  g_object_unref (color);
  return 0;
}

/**
 * newindex metafunction for colors.
 * sets the r,g,b,a components of a color to a number.
 */
static int color_newindex (lua_State *ls)
{
  GeglColor *color
    = *static_cast <GeglColor **> (luaL_checkudata (ls, 1, mtname_color));
  const char *index = lua_tostring (ls, 2);
  double number = luaL_checknumber (ls, 3);
  /*
   * first get the original color, then modify it, then set it again.
   */
  gdouble r, g, b, a;
  gegl_color_get_rgba (color, &r, &g, &b, &a);
  if (strcmp (index, "r") == 0) {
    r = number;
  } else if (strcmp (index, "g") == 0) {
    g = number;
  } else if (strcmp (index, "b") == 0) {
    b = number;
  } else if (strcmp (index, "a") == 0) {
    a = number;
  } else {
    luaL_error (ls, "color has no '%s' attribute.", index);
  }
  gegl_color_set_rgba (color, r, g, b, a);
  return 0;
}

/**
 * index meta function for colors:
 * access the components r,b,g,a from lua.
 */
static int color_index (lua_State *ls)
{
  GeglColor *color
    = *static_cast <GeglColor **> (luaL_checkudata (ls, 1, mtname_color));
  const char *index = lua_tostring (ls, 2);
  gdouble r, g, b, a;
  gegl_color_get_rgba (color, &r, &g, &b, &a);
  if (strcmp (index, "r") == 0) {
    lua_pushnumber (ls, r);
    return 1;
  } else if (strcmp (index, "g") == 0) {
    lua_pushnumber (ls, g);
    return 1;
  } else if (strcmp (index, "b") == 0) {
    lua_pushnumber (ls, b);
    return 1;
  } else if (strcmp (index, "a") == 0) {
    lua_pushnumber (ls, a);
    return 1;
  } else {
    luaL_error (ls, "color has no '%s' attribute.", index);
    return 0;
  }
}

static int color_tostring (lua_State *ls)
{
  GeglColor *color = *static_cast <GeglColor **> (lua_touserdata (ls, 1));
  lua_pushstring (ls, luge::color_to_string (color).c_str ());
  return 1;
}
} /* local namespace */

std::string luge::color_to_string (GeglColor *color)
{
  double r, g, b, a;
  gegl_color_get_rgba (color, &r, &g, &b, &a);
  std::ostringstream oss;
  oss << "rgba(" << r << ',' << g << ',' << b << ',' << a << ")";
  return oss.str ();
}

/**
 * return true, iff the object at idx is a color.
 */
bool luge::is_color (lua_State *ls, int idx)
{
  return luaL_testudata (ls, idx, mtname_color) != 0;
}

/**
 * convert the object at the given index to a color.
 */
GeglColor *luge::to_color (lua_State *ls, int idx)
{
  if (luaL_testudata (ls, idx, mtname_color)) {
    /*
     * already a color object.
     */
    return *(GeglColor **) luaL_checkudata (ls, idx, mtname_color);
  } else if (lua_type (ls, idx) == LUA_TSTRING) {
    std::string str (lua_tostring (ls, idx));
    return gegl_color_new (str.c_str ());
  } else if (lua_type (ls, idx) == LUA_TNUMBER) {
    double value = lua_tonumber (ls, idx);
    GeglColor *color = gegl_color_new (0);
    gegl_color_set_rgba (color, value, value, value, 1);
    return color;
  } else {
    return 0;
  }
}

/**
 * push a color onto the stack.
 */
void luge::push_color (lua_State *ls, GeglColor *color)
{
  void *addr = lua_newuserdata (ls, sizeof color);
  new (addr) GeglColor * (color);
  push_color_metatable (ls);
  lua_setmetatable (ls, -2);
  g_object_ref (color);
}

GeglColor *luge::rgba_color (float r, float g, float b, float a)
{
  GeglColor *color = gegl_color_new (0);
  gegl_color_set_rgba (color, r, g, b, a);
  return color;
}

int luge::color_construct (lua_State *ls)
{
  const int nargs = lua_gettop (ls);
  if (nargs == 1) {
    /*
     * one arguments is a simple conversion, checks for strings or numbers.
     */
    GeglColor *color = luge::to_color (ls, 1);
    push_color (ls, color);
  } else if (nargs == 2) {
    /*
     * create a grey color with an alpha value.
     */
    const float v = luaL_checknumber (ls, 1);
    const float a = luaL_checknumber (ls, 2);
    push_color (ls, luge::rgba_color (v, v, v, a));
  } else if (nargs == 3) {
    /*
     * 3 arguments must make an rgb color.
     */
    const float r = luaL_checknumber (ls, 1);
    const float g = luaL_checknumber (ls, 2);
    const float b = luaL_checknumber (ls, 3);
    push_color (ls, luge::rgba_color (r, g, b, 1));
  } else if (nargs == 4) {
    /*
     * 4 arguments are rgba
     */
    const float r = luaL_checknumber (ls, 1);
    const float g = luaL_checknumber (ls, 2);
    const float b = luaL_checknumber (ls, 3);
    const float a = luaL_checknumber (ls, 4);
    push_color (ls, luge::rgba_color (r, g, b, a));
  } else {
    luaL_error (ls, "'color' cannot handle %d arguments.", nargs);
  }
  return 1;
}

